import axios, { AxiosError, type AxiosResponse, type AxiosStatic, type InternalAxiosRequestConfig } from "axios";
import { postRefreshToken } from "@/utils/api/login.js";
import router from "@/router";
import { ElNotification } from "element-plus";
import { AUTHORIZATION, REFRESH_TOKEN, WHITE_LIST } from "@/utils/config";
import type { IRefreshParam, IRefreshToken } from "@/types/token";
// 是否正在刷新的标记
let isRefreshing = false;
// 重试队列，每一项将是一个待执行的函数形式
let requests: Array<() => void> = [];
export const axiosConfig = () => {
  console.log(import.meta.env.VITE_API_URL);
  // 添加统一的请求接口（也不一定要配置）
  axios.defaults.baseURL = import.meta.env.VITE_API_URL;
  // axios.defaults.timeout = 2500;
  // 添加请求拦截器
  handleAxiosRequestInterceptors(axios);
  /* 添加响应拦截器 */
  handleAxiosResponseInterceptors(axios);
};
/* 用于区分请求，取消重复请求的时候用 */
const requestMap: Map<string, AbortController> = new Map();

/* axios处理请求拦截器 */
function handleAxiosRequestInterceptors(axios: AxiosStatic) {
  axios.interceptors.request.use(
    function (config: InternalAxiosRequestConfig) {
      // 每个请求头中添加 token
      const Token = localStorage.getItem(AUTHORIZATION);
      const controller = new AbortController();
      if (!config.method || !config.url) {
        return config;
      }
      const key = config.method + config.url;
      if (handleJudgmentRequiresCancel(config)) {
        config.signal = controller.signal;
        if (requestMap.has(key)) {
          requestMap.get(key)?.abort();
          // requestMap.delete(key);
          requestMap.set(key, controller);
        } else {
          requestMap.set(key, controller);
        }
      }
      if (Token) {
        config.headers[AUTHORIZATION] = Token; //将token放到请求头发送给服务器
        return config;
      }
      return config;
    },
    function (error) {
      // 对请求错误做些什么
      return Promise.reject(error);
    }
  );
}

/* axios处理响应拦截器 */
function handleAxiosResponseInterceptors(axios: AxiosStatic) {
  axios.interceptors.response.use(
    async (response: AxiosResponse): Promise<AxiosResponse> => {
      if (!response.config.method || !response.config.url) {
        return response;
      }
      /* 删除需求请求的键值对信息 */
      const key = response.config.method + response.config.url;
      if (handleJudgmentRequiresCancel(response.config)) {
        requestMap.delete(key);
      }
      /* 刷新token的参数 */
      const refreshParam: IRefreshParam = {
        refreshToken: ""
      };
      // 对返回的数据进行一些处理
      // 根据后端返回的code 进行判断即可
      if (response.data.status === 2) {
        /* accessToken失效 */
        refreshParam.refreshToken = localStorage.getItem(REFRESH_TOKEN) || "";
        /* 利用refreshToken刷新accessToken */
        if (!isRefreshing) {
          isRefreshing = true;
          try {
            const refreshToken = await postRefreshToken<IRefreshToken, IRefreshParam>(refreshParam);
            /* 重新赋值accessToken与 refreshToken*/
            localStorage.setItem(AUTHORIZATION, refreshToken.result.accessToken);
            localStorage.setItem(REFRESH_TOKEN, refreshToken.result.refreshToken);
            requests.forEach(request => request());
            requests = [];
            isRefreshing = false;
            /* 重新发送上一条请求（这条请求不会再下的else代码中拦截）await不需要加 */
            return axios.request(response.config);
          } catch (error) {
            /* 为设么会走到这里 postRefreshToken的返回是一个失败的Promise对象，就会走到这里*/
            // console.log(1, error);
            return Promise.reject(error);
          }
        }
        // 正在刷新token，将返回一个未执行（未决状态下的）resolve的promise
        // 拦截其他所有的请求，使其处于一个未执行的状态，只有当刷新token请求返回以后，再执行拦截的所有请求
        // 后续处理返回的是一个任务对象（Promise），新任务的状态和数据与该任务对象一致，这样就做到了拦截其他失败的请求

        /* 我感觉这里还有一个问题就是：如果同时有3个请求，response.data.status === 2，那么其中最快的一个请求会执行刷新token的请求。假设刷新token的请求都返回了，而另外两个请求还没有返回，此时遍历requests，它是一个空数组了，导致页面中缺少数据，没能达到重新请求失败的请求的效果。handleInterceptedRequest函数中有解决方案 */
        return handleInterceptedRequest(response);
      }
      return response;
    },
    error => {
      isRefreshing = false;
      /* 为什么会走到这里的原因？状态码为非200的时候 会执行失败的回调函数 */
      // console.log(2, error);
      if (error.response !== undefined) {
        handelAxiosResponseError(error);
        return error;
      } else {
        return Promise.reject(error);
      }
    }
  );
}

/**
 * 重新登录提示框，并跳转到登录页面
 */
function handleMessageLogin() {
  localStorage.removeItem(AUTHORIZATION);
  const redirect = router.currentRoute.value.fullPath;
  ElNotification.warning({ title: "提示", message: "登录过期，请重新登录" });
  /* 跳转到登录页，而后登录成功，还返回当前页 */
  router.replace({
    path: "/login",
    query: {
      redirect
    }
  });
  isRefreshing = false;
}

/* 处理 axios 返回拦截器的 异常信息*/
function handelAxiosResponseError(error: AxiosError) {
  if (!isRefreshing) {
    const title = "错误";
    let message = "";
    if (error.status === 401) {
      handleMessageLogin();
      return;
    } else if (error.status === 403) {
      message = "当前操作没有权限";
    } else if (error.status === 404) {
      message = "服务器找不到给定的资源";
    } else if (error.status === 500) {
      message = "系统异常,请联系管理员";
    } else if (error.status === 503) {
      message = "服务器繁忙，请稍后再试";
    }
    requests = []; /*删除队列中的所有请求，防止重新登录后的，下一次accessToken过期收集数据时，队列中还有数据，导致不必要的数据请求 */
    isRefreshing = false;
    ElNotification.error({ title, message });
  }
}

/* 收集被拦截的请求 */
function handleInterceptedRequest(response: AxiosResponse): Promise<AxiosResponse> {
  return new Promise(resolve => {
    // 将resolve放进队列，用一个函数形式来保存，等token刷新后直接执行
    requests.push(() => {
      resolve(axios.request(response.config));
    });
    /* TODO：如果本地存储中有accessToken，那么就直接 return 执行 axios.request(response.config) ，如果没有，返回一个未决转态的 Promise对象。直到刷新token成功，再执行队列中的函数（这一步就是上面的这一步  requests.forEach(request => request());） */
    /* 这种情况有点极端了，基本上不会出现 */
  });
}

/* 校验请求和方法是否需要做取消上一次请求的操作 */
function handleJudgmentRequiresCancel({ method, url = "" }: InternalAxiosRequestConfig) {
  if (WHITE_LIST.includes(url)) return false;
  /* 只做get请求的取消请求 */
  if (method === "get") return true;
  return false;
}
